﻿using System;
using System.ComponentModel;
using System.Collections.ObjectModel;
using STeaL.Collections.Generic;
using STeaL.Algorithm;
using STeaL.ComponentModel;

namespace RPNcalc {

  public class Calculator : INotifyPropertyChanged {

    #region properties
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<double>    Stack        { get { return stack_;      } }
    public string                          StackTop     { get { return stack_.empty() ? string.Empty : stack_.front().ToString(); } }
    public ObservableCollection<WordEntry> Dictionary   { get { return words_; } }
    public string                          Word         { get { return word_;       } set { word_    = value; PropertyChanged.Notify(this,"Word");    } }
    public string                          Command      { get { return command_;    } set { command_ = value; PropertyChanged.Notify(this,"Command"); } }
    public WordEntry                       SelectedItem { set { if ( value != null ) { Word = value.Key; Command = value.Value; } } }
    public string                          Status       { get { return status_;     } set { status_ = value; PropertyChanged.Notify(this,"Status");   } }
    #endregion

    #region public methods
    public Calculator() {
#if USE_MULTISET
      dictionary_ = new multiset_adaptor<WordEntry>(words_, (x, y) => x.Key.CompareTo(y.Key) <0);
#else
      dictionary_ = new set_adaptor<WordEntry>(words_, (x, y) => x.Key.CompareTo(y.Key) <0);
#endif
    }

    public bool StepExec() {
      string command = Command;
      Word = Utils.Tokenize(ref command);
      Command = command;
      if (!string.IsNullOrEmpty(Word)) {
        double x;
        double y;
        if (double.TryParse(Word, out x)) {
          Status = "数値はプッシュ";
          stack_.push_front(x);
        } else {
          Status = ".XXX は演算";
          switch ( Word ) {
          case ".clr"  : stack_.Clear(); break;
          case ".drop" : stack_.pop_front(); break;
          case ".dup"  : stack_.push_front(stack_.front()); break;
          case ".swap" : stack_.pop_front(out x); stack_.pop_front(out y); stack_.push_front(x); stack_.push_front(y); break;
          case ".neg"  : stack_.pop_front(out x); stack_.push_front(-x); break;
          case ".add"  : stack_.pop_front(out x); stack_.pop_front(out y); stack_.push_front(y + x); break;
          case ".sub"  : stack_.pop_front(out x); stack_.pop_front(out y); stack_.push_front(y - x); break;
          case ".mul"  : stack_.pop_front(out x); stack_.pop_front(out y); stack_.push_front(y * x); break;
          case ".div"  : stack_.pop_front(out x); stack_.pop_front(out y); stack_.push_front(y / x); break;
          case ".sqrt" : stack_.pop_front(out x); stack_.push_front(Math.Sqrt(x)); break;
          default:
#if USE_MULTISET
            Tuple<int,int> range;
            if ( dictionary_.Find(new WordEntry(Word,Command), out range) ) {
              Command = dictionary_[range.Item1].Value + " " + Command;
#else
            int pos = dictionary_.IndexOf(new WordEntry(Word,Command));
            if ( pos >= 0 ) {
              Command = dictionary_[pos].Value + " " + Command;
#endif
              Status = "辞書を引いて置き換え";
            } else {
              Status = "シラネ";
            }
            break;
          }
        } 
        PropertyChanged.Notify(this,"StackTop");
        return true;
      }
      return false;
    }

    public void Edit(string k, string v) {
      k = k.Replace('　',' ').Trim();
      if ( string.IsNullOrEmpty(k) ) return;
      v = v.Replace('　',' ').Trim();
      WordEntry entry = new WordEntry(k,v);
#if USE_MULTISET
      Tuple<int,int> range;
      if ( dictionary_.Find(entry,out range) ) {
        dictionary_.RemoveAt(range.Item1);
      }
#else
      int pos = dictionary_.IndexOf(entry);
      if ( pos >= 0 ) {
        dictionary_.RemoveAt(pos);
      }        
#endif
      if (v.Length != 0) {
        dictionary_.Add(entry);
      }
    }
    #endregion

    #region privates
    private ObservableCollection<double>    stack_ = new ObservableCollection<double>();
    private ObservableCollection<WordEntry> words_ = new ObservableCollection<WordEntry>();
#if USE_MULTISET
    private multiset_adaptor<WordEntry>     dictionary_;
#else
    private set_adaptor<WordEntry>          dictionary_;
#endif
    private string                          word_;
    private string                          command_;
    private string                          status_ = "ぎゃくポでんたく";
    #endregion
  }
}
